<?php
use Pinenut\Model2;
use Pinenut\Model2\Adapter\Mongo;
use Pinenut\Model2\Finder;
/**
 * Предложения пользователей
 *
 * Используется для сохранения возможных действий над системой
 * Которые после их подтверждения другими пользователя реализуются
 * автоматически
 *
 * Используется для двухтапной автомодерации содержимого сайта
 * А в рамках игры Гуру - для удаления неверных фактов и выводов,
 * переименования и объединения фактов и выводов
 *
 * При создании предложения по типу определяем метод и сущность,
 * которые нужно вызвать для подготовки данных
 * При решении подтвердить предложение - вызываем метод сущности,
 * подготавливающий вопрос на подтверждение.
 * При решении о действии (подтвердило достаточно людей) - вызываем
 * третий метод сущности, осуществляющий предложение
 *
 * @author kosh
 *        
 */
class UserProposal extends Model2 {
	const MODEL = 'model';
	const STAGE_PREPARE = 'prepare';
	const STAGE_CREATE = 'create';
	const STAGE_ACTION = 'action';
	const MIN = 'min';
	const MAX = 'max';
	const PERCENTAGE = 'percentage';
	
	/**
	 *
	 * @var \Pinenut\Config
	 */
	static $config = '\UserProposalConfig';
	
	/**
	 * тип действия в себе несет и сущности и действие (через конфу)
	 *
	 * @var string
	 */
	public $type;
	
	/**
	 * Идентификатор сущности, над которой производится действие
	 * По нему и типу идёт группировка счетов некоторых действий,
	 * поэтому вынесен из праметров
	 *
	 * @var string
	 */
	public $id;
	
	/**
	 * Массив параметров, в случае с переименованием например
	 * старое и новое значение и т.д.
	 *
	 * @var array
	 */
	public $params = array ();
	
	/**
	 *
	 * @var string
	 */
	public $userSession;
	
	/**
	 * Массив идентификаторов сессий/пользователей
	 *
	 * @var string[]
	 */
	public $surveysSessions = array ();
	
	/**
	 * Количество заданных вопросов
	 *
	 * @var integer
	 */
	public $surveysCount = 0;
	
	/**
	 * Количество подтверждающих необходимость действия ответов
	 *
	 * @var integer
	 */
	public $confirmedCount = 0;
	
	/**
	 * Количество отвергающих ответов
	 *
	 * @var integer
	 */
	public $rejectedCount = 0;
	
	/**
	 * Принято решение
	 *
	 * @var bool
	 */
	public $resolution = false;
	function fields() {
		return array_merge ( parent::fields (), array (
				'type' => self::STRING,
				'id' => self::STRING,
				'params' => self::MIXED,
				'surveysSessions' => self::MIXED,
				'surveysCount' => self::INT,
				'confirmedCount' => self::INT,
				'rejectedCount' => self::INT,
				'resolution' => self::BOOLEAN 
		) );
	}
	
	/**
	 * Готовим данные для вывода вопроса о предложении
	 *
	 * @param string $type        	
	 * @param string $id        	
	 */
	public static function prepareProposal($type, $id) {
		$data = static::doMethod ( $type, static::STAGE_PREPARE, $id );
		$data['id'] = $id;
		$data['type'] = $type;
		return $data;
	}
	
	/**
	 * Создаём предложение пользователя
	 *
	 * @param string $session        	
	 * @param string $type        	
	 * @param string $id        	
	 * @param array $params        	
	 */
	public static function createProposal($session, $type, $id, $params = array()) {
		$proposal = new static (); // тут можно ыло бы не создават их, 
									// а проверять вначале на уже существующие в базе
		$proposal->type = $type;
		$proposal->id = $id;
		$proposal->params = static::doMethod ( $type, static::STAGE_CREATE, $id, $params );
		$proposal->save ();
	}
	
	/**
	 * Готовим вопрос о подтверждении
	 * Пока случайным образом, не повторяясь
	 *
	 * @param strinng $session        	
	 */
	public static function surveyQuestion($session) {
		$finder = static::find ()->where ( 'resolution', false )->where ( 'surveysSessions', $session, Finder::OP_NIN );
		$count = $finder->count ();
		if ($count) {
			$offset = rand ( 0, $count );
			$proposal = $finder->offset ( $offset )->one ();
			$proposal->push ( 'surveysSessions', $session );
			$proposal->inc ( 'surveysCount', 1 );
			$proposal->save ();
			return $proposal;
		}
	}
	
	/**
	 * Сохраняем ответ, решаем нужно ли выполнить действие
	 *
	 * @param unknown $session        	
	 * @param unknown $id        	
	 * @param unknown $answer        	
	 */
	public static function surveyAnswer($session, $id, $answer) {
		// ищем соответствующее предложение
		$proposal = static::find ()->where ( '_id', $id )->where ( 'surveysSessions', $session, Finder::OP_IN )->one ();
		if ($proposal) {
			// обновляем запись
			$proposal->inc ( $answer > 0 ? 'confirmedCount' : 'rejectedCount', 1 );
			$proposal->save ();
			
			// запускаем проверку и действие
			$proposal->action ();
		}
	}
	protected function action() {
		$configClassName = static::$config;
		$config = $configClassName::get ( $this->type );
		
		if (null === $config) {
			throw new Exception ( 'Тип предложения пользователя не корректен' );
		}
		
		$min = isset ( $config [static::MIN] ) ? $config [static::MIN] : 3;
		$max = isset ( $config [static::MAX] ) ? $config [static::MAX] : 10;
		$percentage = (isset ( $config [static::PERCENTAGE] ) ? $config [static::PERCENTAGE] : 80) / 100;
		
		$count = $this->rejectedCount + $this->confirmedCount;
		$proposalPercentage = $this->confirmedCount / ($count > 0 ? $count : 1);
		
		if ($this->surveysCount > $min && $proposalPercentage >= $percentage) {
			// если набралли минимум голосов и ппроцент подтверждающих выходит нормальным
			// то отыгрываем действие и помечаем предложение как обработанное
			static::doMethod ( $this->type, static::STAGE_ACTION, $this->id );
		} elseif ($this->surveysCount > $max) {
			// иначе если количество отпросов превышает максимальное - так же помечаем как обработанное,
			// но ничего не делаем
		} else {
			return;
		}
		
		$this->resolution = true;
		$this->save ();
	}
	
	/**
	 * Исполняем метод модели, соответствующий запрашиваемому этапу
	 *
	 * @param string $type        	
	 * @param string $stage        	
	 * @param string $id        	
	 * @param array $params        	
	 * @throws Exception
	 * @return array
	 */
	protected function doMethod($type, $stage, $id, $params = array()) {
		$configClassName = static::$config;
		$config = $configClassName::get ( $type );
		
		if (null === $config) {
			throw new Exception ( 'Тип предложения пользователя не корректен' );
		}
		
		$className = $config [static::MODEL];
		if (isset ( $config [$stage] )) {
			$methodName = $config [$stage];
			
			$params = $className::$methodName ( $id, $params );
		}
		
		return $params;
	}
}